/*
 * @Author: 鱿鱼溪学院
 * @Date: 2025-04-05 22:22:21
 * @LastEditTime: 2025-07-08 23:00:50
 * @LastEditors: 鱿鱼溪学院
 * @Description:
 * @FilePath: \yyx_ser_ts\app\middleware\UserMiddleware.ts
 * Copyright 鱿鱼溪学院 by edu v1.0
 * 天道酬勤
 */
import { Context, Next } from 'koa'
import response from '../../utils/response'
import AdminService from '../service/AdminService'
import {
  PasswordValidationException,
  UserValidationException
} from '../exception/HttpException'
import bcrypt from 'bcryptjs'
import { decrypt } from '../../utils/aes'
import UserService from '../service/UserService'
import logger from '../logger'
import redis from '../db/redis' // 引入 Redis 实例

/**
 * 用户验证中间件
 * 该中间件用于解密并验证用户提交的用户名和密码
 * @param ctx 上下文对象，包含请求和响应的相关信息
 * @param next 下一个中间件的执行函数
 */
export const userValidator = async (ctx: Context, next: Next) => {
  // 从请求体中获取加密后的用户名和密码
  const { username: encryptedUsername, password: encryptedPassword } =
    ctx.request.body

  console.log('userValidator', encryptedUsername, encryptedPassword)

  // 解密用户名和密码，并捕获解密过程中可能出现的异常
  let username: string
  let password: string

  try {
    username = decrypt(encryptedUsername)
    password = decrypt(encryptedPassword)
  } catch (error: any) {
    // 解密失败时，记录错误日志并抛出自定义异常
    throw new UserValidationException(error, '用户名或密码解密失败', 20003)
  }

  // 校验解密后的用户名和密码是否有效
  if (
    !username ||
    !password ||
    typeof username !== 'string' ||
    typeof password !== 'string'
  ) {
    return response.failResponse(ctx, '用户名或密码无效!')
  }

  // 验证用户名和密码的长度（可根据实际需求调整）
  if (username.length < 2 || username.length > 20) {
    throw new UserValidationException([], '用户名长度不符合要求')
  }
  if (password.length < 6 || password.length > 20) {
    throw new UserValidationException([], '密码长度不符合要求')
  }

  // 将解密后的用户名和密码存储到 ctx.state 中，供后续中间件使用
  ctx.state.decryptedUsername = username
  ctx.state.decryptedPassword = password

  // 清理原始加密数据，减少敏感信息暴露
  encryptedUsername &&
    (encryptedUsername as any).fill &&
    encryptedUsername.fill(0)
  encryptedPassword &&
    (encryptedPassword as any).fill &&
    encryptedPassword.fill(0)

  // 执行下一个中间件
  await next()
}

// 定义常量，便于管理和国际化支持
const ERROR_DECRYPTION_FAILED = '解密失败'
const ERROR_INVALID_DATA = '数据无效'

/**
 * 解密中间件
 * 该中间件用于解密请求体中的用户名、密码、手机号或验证码
 * @param ctx 上下文对象，包含请求和响应的相关信息
 * @param next 下一个中间件的执行函数
 */
export const decryptMiddleware = async (ctx: Context, next: Next) => {
  const { username, password, phone, verificationCode } = ctx.request.body

  // 存储解密后的数据
  let decryptedUsername: string | undefined
  let decryptedPassword: string | undefined
  let decryptedPhone: string | undefined
  let decryptedVerificationCode: string | undefined

  //console.log('decryptMiddleware', username, password, phone, verificationCode)

  // 解密用户名
  if (username) {
    try {
      decryptedUsername = decrypt(username)
    } catch (error: any) {
      throw new UserValidationException(error, ERROR_DECRYPTION_FAILED, 20003)
    }
  }

  // 解密密码
  if (password) {
    try {
      decryptedPassword = decrypt(password)
    } catch (error: any) {
      throw new UserValidationException(error, ERROR_DECRYPTION_FAILED, 20003)
    }
  }

  // 解密手机号
  if (phone) {
    try {
      decryptedPhone = decrypt(phone)
    } catch (error: any) {
      throw new UserValidationException(error, ERROR_DECRYPTION_FAILED, 20003)
    }
  }

  // 解密验证码
  if (verificationCode) {
    try {
      decryptedVerificationCode = decrypt(verificationCode)
    } catch (error: any) {
      throw new UserValidationException(error, ERROR_DECRYPTION_FAILED, 20003)
    }
  }

  // console.log(
  //   'decryptMiddleware',
  //   decryptedUsername,
  //   decryptedPassword,
  //   decryptedPhone,
  //   decryptedVerificationCode
  // )

  // 校验解密后的数据是否有效
  if (
    decryptedUsername &&
    (typeof decryptedUsername !== 'string' ||
      decryptedUsername.length < 2 ||
      decryptedUsername.length > 20)
  ) {
    console.log('decryptMiddleware', ERROR_INVALID_DATA)
    return response.failResponse(ctx, ERROR_INVALID_DATA)
  }
  if (
    decryptedPassword &&
    (typeof decryptedPassword !== 'string' ||
      decryptedPassword.length < 6 ||
      decryptedPassword.length > 20)
  ) {
    return response.failResponse(ctx, ERROR_INVALID_DATA)
  }
  if (
    decryptedPhone &&
    (typeof decryptedPhone !== 'string' ||
      !/^1[3-9]\d{9}$/.test(decryptedPhone))
  ) {
    return response.failResponse(ctx, ERROR_INVALID_DATA)
  }
  if (
    decryptedVerificationCode &&
    (typeof decryptedVerificationCode !== 'string' ||
      decryptedVerificationCode.length !== 4) // 假设验证码长度为4
  ) {
    return response.failResponse(ctx, ERROR_INVALID_DATA)
  }

  // 将解密后的数据存储到 ctx.state 中，供后续中间件使用
  ctx.state.decryptedUsername = decryptedUsername
  ctx.state.decryptedPassword = decryptedPassword
  ctx.state.decryptedPhone = decryptedPhone
  ctx.state.decryptedVerificationCode = decryptedVerificationCode

  // 清理原始加密数据，减少敏感信息暴露
  if (username && (username as any).fill) {
    ;(username as any).fill(0)
  }
  if (password && (password as any).fill) {
    ;(password as any).fill(0)
  }
  if (phone && (phone as any).fill) {
    ;(phone as any).fill(0)
  }
  if (verificationCode && (verificationCode as any).fill) {
    ;(verificationCode as any).fill(0)
  }

  //return response.success(ctx, ctx.state, '获取成功')

  // 执行下一个中间件
  await next()
}

/**
 * 验证用户是否存在
 * 此函数主要用于在用户注册或登录时验证用户名是否已存在
 * 它通过查询数据库中的用户信息来确定用户名的唯一性
 * 如果用户存在，则返回错误响应，提示用户名已存在
 * 如果用户不存在或查询过程中出现错误，则捕获异常并抛出
 *
 * @param {Context} ctx - Koa框架的上下文对象，包含请求和响应的相关信息
 * @param {Next} next - Koa框架的下一个中间件执行函数
 * @returns {Promise<void>} - 异步函数，无返回值
 */
const ERROR_INVALID_USER = '用户名格式不正确或者为空！'
const USERNAME_MIN_LENGTH = 3 // 假设用户名最小长度为3
const USERNAME_MAX_LENGTH = 20
export const verifyUser = async (ctx: Context, next: Next) => {
  //1.获取数据
  const { decryptedUsername: username } = ctx.state
  console.log(username, 'username')
  // return response.success(ctx, username)
  if (
    !username ||
    typeof username !== 'string' ||
    username.length < USERNAME_MIN_LENGTH ||
    username.length > USERNAME_MAX_LENGTH
  ) {
    return response.failResponse(ctx, ERROR_INVALID_USER, [], 400)
  }

  // return response.success(ctx, { username })

  try {
    // 调用UserService的getUserInfo方法查询用户信息
    const result = await UserService.getUserInfo({ username })

    console.log(result, 'result')
    //合理性
    if (result) {
      // 如果查询到用户，返回错误提示用户名已存在
      return response.failResponse(ctx, '用户名已存在！', [], 409)
      // throw new UserValidationException('', '用户名已存在！', 20001, 409)
    }
  } catch (err) {
    // console.error('获取用户信息失败', err) //写入日志

    // 如果查询过程中出现错误，抛出自定义异常
    throw new UserValidationException(err, '获取用户信息失败!')
    //不要忘记写return
    // return
  }

  // 继续执行下一个中间件
  await next()
}

// 定义常量，便于管理和国际化支持
const ERROR_PHONE_EXISTS = '手机号已存在,请换一个手机号，或者直接进入登录！'
const ERROR_INVALID_PHONE = '手机号格式不正确或者为空！'
const ERROR_FETCH_PHONE_INFO = '获取手机号信息失败!'
const PHONE_REGEX = /^1[3-9]\d{9}$/ // 假设手机号码格式为11位，以1开头，第二位为3-9之间的数字

/**
 * 验证手机号是否存在
 * 此函数主要用于在用户注册时验证手机号是否已存在
 * 它通过查询数据库中的用户信息来确定手机号的唯一性
 * 如果手机号存在，则返回错误响应，提示手机号已存在
 * 如果手机号不存在或查询过程中出现错误，则捕获异常并抛出
 *
 * @param {Context} ctx - Koa框架的上下文对象，包含请求和响应的相关信息
 * @param {Next} next - Koa框架的下一个中间件执行函数
 * @returns {Promise<void>} - 异步函数，无返回值
 */
export const verifyPhone = async (ctx: Context, next: Next) => {
  // 1. 获取数据
  const { decryptedPhone: phone } = ctx.state
  console.log(phone, 'phone.......')

  // 校验手机号码是否有效
  if (!phone || typeof phone !== 'string' || !PHONE_REGEX.test(phone)) {
    return response.failResponse(ctx, ERROR_INVALID_PHONE, [], 400)
  }

  try {
    // 调用UserService的getPhoneInfo方法查询用户信息
    const result = await UserService.getUserInfo({ phone })

    console.log(result)
    // 合理性
    if (result) {
      // 如果查询到手机号，返回错误提示手机号已存在
      return response.failResponse(ctx, ERROR_PHONE_EXISTS, '', 409, 20002)
    }
  } catch (err) {
    // 记录错误日志
    logger.error(ERROR_FETCH_PHONE_INFO, err)

    // 如果查询过程中出现错误，抛出自定义异常
    throw new UserValidationException(err, ERROR_FETCH_PHONE_INFO)
  }

  // 继续执行下一个中间件
  await next()
}

// 定义常量，便于管理和国际化支持
const ERROR_INVALID_VERIFICATION_CODE = '验证码无效或已过期！'

/**
 * 验证验证码
 * 此函数主要用于验证用户提交的验证码是否有效
 * 它通过查询 Redis 中的验证码信息来确定验证码的有效性
 * 如果验证码无效或已过期，则返回错误响应
 * 如果验证过程中出现错误，则捕获异常并抛出
 *
 * @param {Context} ctx - Koa框架的上下文对象，包含请求和响应的相关信息
 * @param {Next} next - Koa框架的下一个中间件执行函数
 * @returns {Promise<void>} - 异步函数，无返回值
 */
export const verifyVerificationCode = async (ctx: Context, next: Next) => {
  const { decryptedVerificationCode, decryptedPhone } = ctx.state

  console.log(decryptedVerificationCode, 'decryptedVerificationCode....')
  console.log(decryptedPhone, 'decryptedPhone')
  //  return response.success(ctx, { decryptedVerificationCode, decryptedPhone })

  const redisKey = `smsCode:${decryptedPhone}`

  // 校验验证码是否存在
  if (!decryptedVerificationCode) {
    return response.failResponse(ctx, ERROR_INVALID_VERIFICATION_CODE, [], 400)
  }

  try {
    // 从 Redis 中获取存储的验证码
    const storedCode = await redis.get(redisKey)
    // const storedCode = await redis.get(`smsCode`)

    //console.log(storedCode, 'storedCode')
    if (!storedCode || storedCode !== decryptedVerificationCode) {
      logger.error('验证码验证失败', storedCode)
      console.log('.......')
      return response.failResponse(
        ctx,
        ERROR_INVALID_VERIFICATION_CODE,
        [redisKey],
        400
      )
    }
    //  return response.success(ctx, { storedCode, redisKey }, '验证码验证成功！')
    // 验证码验证通过，删除 Redis 中的验证码
    await redis.del(`verification_code:${decryptedPhone}`)
  } catch (err) {
    // 记录错误日志
    logger.error('验证码验证失败', err)

    // 如果验证过程中出现错误，抛出自定义异常
    throw new UserValidationException(err, '验证码验证失败!')
  }

  // 继续执行下一个中间件
  await next()
}

// ... existing code ...

//密码加密处理
export const crpytPassword = async (ctx: Context, next: Next) => {
  const { decryptedPassword } = ctx.state
  console.log(decryptedPassword, 'body的password')

  if (!decryptedPassword) {
    throw new PasswordValidationException('', '密码不能为空！')
  }

  //加盐
  const salt = bcrypt.genSaltSync(10)
  //hash保存的是密文
  const hash = bcrypt.hashSync(decryptedPassword, salt)
  ctx.state.decryptedPassword = hash

  console.log(hash, '加密后的password')

  await next()
}

//登录验证
// export const verifyLogin = async (ctx: Context, next: Next) => {
//   console.log('verifyLogin')
//   //1.判断用户是否存在
//   const { username, password } = ctx.request.body
//   response.success(ctx, { username, password }) //返回用户名，用于前端显示
//   try {
//     const res = await AdminService.getAdminInfo({ username })

//     if (!res) {
//       return response.failResponse(ctx, '用户不存在！', username)
//     }

//     console.log(
//       res.password,
//       password,
//       bcrypt.compareSync(password, res.password)
//     )
//     // return
//     //2.密码是否匹配(不匹配，报错)
//     if (!bcrypt.compareSync(password, res.password)) {
//       return response.failResponse(ctx, '密码不匹配！', username)
//     }
//   } catch (err) {
//     console.error(err) //写入日志
//     throw new UserValidationException(err, '用户登录失败！', 20003)
//   }

//   await next()
// }

export const verifyLogin = async (ctx: Context, next: Next) => {
  // 从 ctx.state 中获取解密后的用户名和密码
  const { decryptedUsername, decryptedPassword } = ctx.state

  // return response.success(ctx, {
  //   username: decryptedUsername,
  //   password: decryptedPassword
  // })

  // 输入参数校验
  if (!decryptedUsername || !decryptedPassword) {
    logger.error('用户名或密码不能为空！')
    return response.failResponse(ctx, '用户名或密码不能为空！', null)
  }

  try {
    // 查询用户信息
    const res = await AdminService.getAdminInfo({ username: decryptedUsername })

    if (!res) {
      logger.error('登录失败，请检查用户名或密码！')
      // 统一错误信息，避免泄露用户名是否存在
      return response.failResponse(ctx, '登录失败，请检查用户名或密码！', null)
    }

    // 异步密码比较
    const isPasswordMatch = await bcrypt.compare(
      decryptedPassword,
      res.password
    )
    if (!isPasswordMatch) {
      // 统一错误信息，避免泄露密码是否匹配
      return response.failResponse(ctx, '登录失败，请检查用户名或密码！', null)
    }

    // 登录成功，返回用户名用于前端显示
    response.success(ctx, { username: decryptedUsername })
  } catch (err) {
    console.error('登录验证失败:', err) // 安全的日志记录
    return response.failResponse(ctx, '系统错误，登录失败！', null)
  }

  await next()
}
